
using UnityEngine;

namespace KunURP
{
    public static class CameraExtension
    {
        public static Vector4 GetProjectionExtents(this Camera camera)
        {
            return GetProjectionExtents(camera, 0.0f, 0.0f);
        }

        public static Vector4 GetProjectionExtents(this Camera camera, float texelOffsetX, float texelOffsetY)
        {
            if (camera == null)
                return Vector4.zero;

            float oneExtentY = camera.orthographic ? camera.orthographicSize : Mathf.Tan(0.5f * Mathf.Deg2Rad * camera.fieldOfView);
            float oneExtentX = oneExtentY * camera.aspect;
            float texelSizeX = oneExtentX / (0.5f * camera.pixelWidth);
            float texelSizeY = oneExtentY / (0.5f * camera.pixelHeight);
            float oneJitterX = texelSizeX * texelOffsetX;
            float oneJitterY = texelSizeY * texelOffsetY;

            return new Vector4(oneExtentX, oneExtentY, oneJitterX, oneJitterY);// xy = frustum extents at distance 1, zw = jitter at distance 1
        }

        public static Vector4 GetProjectionExtents(this Camera camera, Camera.StereoscopicEye eye)
        {
            return GetProjectionExtents(camera, eye, 0.0f, 0.0f);
        }

        public static Vector4 GetProjectionExtents(this Camera camera, Camera.StereoscopicEye eye, float texelOffsetX, float texelOffsetY)
        {
            Matrix4x4 inv = Matrix4x4.Inverse(camera.GetStereoProjectionMatrix(eye));
            Vector3 ray00 = inv.MultiplyPoint3x4(new Vector3(-1.0f, -1.0f, 0.95f));
            Vector3 ray11 = inv.MultiplyPoint3x4(new Vector3(1.0f, 1.0f, 0.95f));

            ray00 /= -ray00.z;
            ray11 /= -ray11.z;

            float oneExtentX = 0.5f * (ray11.x - ray00.x);
            float oneExtentY = 0.5f * (ray11.y - ray00.y);
            float texelSizeX = oneExtentX / (0.5f * camera.pixelWidth);
            float texelSizeY = oneExtentY / (0.5f * camera.pixelHeight);
            float oneJitterX = 0.5f * (ray11.x + ray00.x) + texelSizeX * texelOffsetX;
            float oneJitterY = 0.5f * (ray11.y + ray00.y) + texelSizeY * texelOffsetY;

            return new Vector4(oneExtentX, oneExtentY, oneJitterX, oneJitterY);// xy = frustum extents at distance 1, zw = jitter at distance 1
        }

        public static Matrix4x4 GetProjectionMatrix(this Camera camera)
        {
            return GetProjectionMatrix(camera, 0.0f, 0.0f);
        }

        public static Matrix4x4 GetProjectionMatrix(this Camera camera, float texelOffsetX, float texelOffsetY)
        {
            if (camera == null)
                return Matrix4x4.identity;

            Vector4 extents = GetProjectionExtents(camera, texelOffsetX, texelOffsetY);

            float cf = camera.farClipPlane;
            float cn = camera.nearClipPlane;
            float xm = extents.z - extents.x;
            float xp = extents.z + extents.x;
            float ym = extents.w - extents.y;
            float yp = extents.w + extents.y;

            if (camera.orthographic)
                return Matrix4x4Extension.GetOrthographicProjection(xm, xp, ym, yp, cn, cf);
            else
                return Matrix4x4Extension.GetPerspectiveProjection(xm * cn, xp * cn, ym * cn, yp * cn, cn, cf);
        }
    }
}